Skip to content

feat(keep): --max-latency-ms as a constraint on every promotion - #1297

Open
rpoornac wants to merge 2 commits into
mainfrom
feat/max-latency-ms
Open

feat(keep): --max-latency-ms as a constraint on every promotion#1297
rpoornac wants to merge 2 commits into
mainfrom
feat/max-latency-ms

Conversation

@rpoornac

@rpoornac rpoornac commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Why

The optimizer maximized output_throughput and nothing else. Latency was
measured, reported, and fed to the prompts, but no latency number could block a
KEEP.

That is survivable for a lever that raises throughput without touching
per-request latency. It is unsafe for any lever that raises throughput by
making each stream slower — against a throughput-only gate, such a lever does
not merely tolerate a latency regression, it selects for the largest one on
offer.

What

--max-latency-ms names a ceiling on mean end-to-end latency. It is a
constraint, not a target, so it sits outside the --target-* mutually-exclusive
group and combines with them.

Enforcement lives at _lift_to_current_best, the single choke point that writes
current_best. That means it holds for explore, kernel, specialist and integrate
winners alike, rather than only for the lane that got wired first. Explore
applies it a round earlier as well, which keeps an over-budget variant from being
folded onto the stack and becoming the anchor the rest of the batch is graded
against, and gives the ledger a latency reason for the REVERT instead of a
promotion refused later with no round to attribute it to.

Fail-closed

Off by default; KEEP behaviour is exactly as it was when unset.

When set, the gate fails closed: a candidate that reported no end-to-end latency
is refused, because an unmeasured constraint is not a satisfied one. This makes
latency part of the measurement contract for the promotion paths, so the lift
dicts each lane hands to the choke point now carry their measured latency — that
way the fail-closed rule refuses the untimed, not the unplumbed.
latency_fields_from_result centralizes both halves of that lookup: the
spellings in flight across the executors (e2el_mean_ms, mean_e2el_ms,
e2el_ms, and ttft_ms / itl_ms for the other two figures), and the one layer
of nesting integrate_patch uses when it returns its measurement under
bench_result. A lane reporting a different alias, or reporting a layer down, is
therefore not misread as unmeasured — the gate grades the workload rather than
the wiring.

The baseline is the one exception: it is the reference point, so an over-budget
baseline warns rather than refuses. It warns loudly, because every subsequent
candidate will then fail the gate. The report compares the kept configuration
against the ceiling rather than asserting it satisfies it, since that exempt
baseline can legitimately occupy the slot while over budget.

Refusals are recorded to SharedState.latency_refusals (action, variant, tput,
observed latency, budget, reason) and render into the orchestration prompt as
=== Latency budget (constraint) ===. Without that, a session that ends near
baseline is indistinguishable from one that exhausted its headroom, and the
router cannot tell a binding SLA from an exhausted search space.

Tests

89 tests in test_latency_budget.py: resolution precedence, the predicate, the
field aliases and nesting, call-site wiring for all seven promotion paths, the
lanes' real result shapes, the report comparison, the prompt block, and each tier
of the resume precedence.

Review notes

This was split out of #1288, where the flag existed but only ExploreExecutor
honoured it while the help text claimed otherwise. It is orthogonal to the
partition work: the constraint applies to any throughput-for-latency trade, so
it is reviewable and useful on its own.

Rebased onto current main after review. That dropped two pieces of the original
diff whose machinery no longer exists upstream: the StackRebenchResult latency
field (the post-KEEP confirmation round was removed) and the framework lane's
lift (the framework_agent action was retired; those candidates now promote
through integrate_patch, which carries the gate).

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

CI E2E report — ✅ Succeeded

item value
result ✅ Succeeded
model Qwen/Qwen3-0.6B (dense)
resources 1× GPU, TP=1
PR branch feat/max-latency-ms
commit 4b765ed06600f06e1e7a54ee5ad2c4e6be2cd541
session_id 849bc49d-61b1-457d-8505-0c3c22609245
queue → dispatch 0s
run time 171m 17s
total 171m 17s

details

@ZhengGong-amd

Copy link
Copy Markdown
Collaborator
  1. fail-closed silently kills promotion for integrate_patch / framework_agent / specialist / GEAK lanes. Their result dicts never populate e2el_mean_ms (verified: 0 occurrences of e2el in integrate_patch.py, framework_agent.py, specialists/rebench.py). Once any --max-latency-ms is set, latency_from_result returns None for these lanes and every KEEP is refused via latency_unmeasured_under_budget — regardless of actual latency. This only surfaces as a log.warning, so operators see "nothing KEEPs anymore" with no diagnosis. Fix: populate e2el_mean_ms from VariantResult.e2el_mean_ms in the three bench dicts (and fix the already-broken ttft_ms/itl_msttft_mean_ms/tpot_mean_ms while there), or reuse the existing disk-fallback reconstruction in sessions.py. Please also add a test that exercises _lift_to_current_best with each executor's real result shape — the current test hardcodes e2el_mean_ms on every lane and would not have caught this.

  2. The report claims "within budget" unconditionally. report.py's best-config line appends ", within budget" without ever comparing kept_e2el to the budget. Since baseline is allowed to exceed budget (warn-only) and still gets written to current_best, the report can state "1211.0 ms, within budget" against a 200 ms budget. Fix: compare against the budget and label accordingly.

  3. orchestration.md tells the model to read latency_refusals, but nothing renders it into any prompt section (render.py / conversation.py have zero references). Fix: add a Latency budget (constraint) prompt section when a budget is set, or drop the pointer to an invisible field.

  4. Resume path clears the env-layer budget before restoring it. _export_latency_budget(args.max_latency_ms) runs unconditionally before the if args.resume_from: branch, so on resume without the flag it pops HYPERLOOM_MAX_LATENCY_MS before _restore_latency_budget_from_state ever reads it — the documented "CLI > env > archived state" priority is dead code for the env layer. Fix: guard the :1772 export the same way _export_workload_envs_for_optimize already is (only when not args.resume_from).

  5. SKILL.md's operator→CLI flag table doesn't list --max-latency-ms. Since this table is documented as the source of truth for forwarding user-stated flags, a user's latency SLA stated in prompt form will be silently dropped. Please add it.

rpoornac and others added 2 commits August 30, 2026 20:12
The optimizer maximized output_throughput and nothing else. Latency was
measured, reported and fed to the prompts, but no latency number could
block a KEEP. That is survivable for a lever that raises throughput
without touching per-request latency, and unsafe for any lever that
raises throughput *by* making each stream slower: against a
throughput-only gate such a lever does not merely tolerate a latency
regression, it selects for the largest one on offer.

--max-latency-ms names a ceiling on mean end-to-end latency. It is a
constraint rather than a target, so it sits outside the --target-*
mutually-exclusive group and combines with them.

Enforcement is at _lift_to_current_best, the single choke point that
writes current_best, so it holds for explore, kernel, specialist and
integrate winners alike rather than only for the lane wired first.
Explore applies it a round earlier too, which keeps an over-budget
variant from being folded onto the stack and becoming the anchor the rest
of the batch is graded against, and gives the ledger a latency reason for
the REVERT rather than a promotion refused later with no round to
attribute it to.

Off by default, leaving KEEP behaviour exactly as it was. When set the
gate fails closed: a candidate that reported no end-to-end latency is
refused, since an unmeasured constraint is not a satisfied one. The lift
dicts that lanes hand to the choke point carry their measured latency so
that fail-closed rule refuses the untimed rather than the unplumbed, and
latency_from_result centralizes the spellings of the field in flight
across the executors.

Refusals are recorded on SharedState and listed in the report: a
constrained session that ends near its baseline is otherwise
indistinguishable from one that found no headroom, and the two call for
opposite responses. A baseline already over budget warns rather than
failing, since it is the reference the run is measured against.

Co-authored-by: Cursor <cursoragent@cursor.com>
A fail-closed gate is only as good as what reaches it, and the bench
lanes reached it with nothing. integrate_patch and the specialist rebench
build their result dicts with no end-to-end latency at all, and report
the other two figures under the GEAK spellings (ttft_ms / itl_ms) that
the breakdown collectors read -- so the gate, which looked only for the
canonical name on the top level, saw an untimed candidate. Under any
--max-latency-ms that is not a cosmetic gap: the gate refuses the untimed
by design, so those lanes lost every KEEP they would ever have made,
whatever their latency, visible only as a log.warning.

Two halves. Those dicts now carry end-to-end latency, in the same
spelling as the siblings beside them so the collector contract is
untouched. And latency_fields_from_result normalizes what promotion
reads: the spellings in flight, plus the one layer of nesting
integrate_patch uses when it returns the measurement under bench_result.
The lift dicts go through it, so the gate grades the workload rather than
the wiring, and current_best carries the latency it was graded on.

The report claimed "within budget" without comparing. The gate exempts
the baseline -- it is the reference the run is measured against, not a
candidate -- so that slot can legitimately hold an over-budget figure,
and the unconditional label asserted the opposite in the one place an
operator would look to catch it. It now compares, and says plainly that
an over-budget best config means nothing has yet come in under the SLA.

orchestration.md told the model to read latency_refusals, which no prompt
contained. SharedState.to_latency_budget_summary renders the constraint
and its recent refusals as "=== Latency budget (constraint) ===", so the
routing guidance points at something the model can see: under a budget a
throughput gain no longer predicts a KEEP, and a growing refusal list
means the SLA is binding rather than the search space exhausted.

The resume path exported the budget before the resume branch, so with no
flag it popped HYPERLOOM_MAX_LATENCY_MS before the restore could read it
and the documented CLI > env > archived-state chain had a dead middle
link. The export is now fresh-launch only, matching the workload envs
beside it. Restoring from env also no longer hands an unparseable value
straight to float(): a stale shell variable falls through to the
archived budget rather than ending the resume.

SKILL.md's flag table, documented as the source of truth for forwarding
an operator's stated values, gains the --max-latency-ms row it was
missing; without it a latency SLA stated in prompt form was dropped.

Tests: 30 -> 57 in test_latency_budget.py. The new ones drive the lanes'
real result shapes rather than a dict that already complies, pin the
canonical names against a VariantResult rename, and cover the report
comparison, the prompt block, and each tier of the resume precedence.

Co-authored-by: Cursor <cursoragent@cursor.com>
@rpoornac
rpoornac force-pushed the feat/max-latency-ms branch from cfb2428 to 4b765ed Compare August 30, 2026 20:46
@rpoornac

Copy link
Copy Markdown
Collaborator Author

All five addressed in 4b765ed06. The branch is also rebased onto current main, which matters for two of these because upstream deleted machinery this PR was built on — details at the end.

1. Fail-closed killed promotion for the bench lanes. Confirmed, and the shape is slightly different on current main than when you filed it. main has since fixed the attribute reads (rb.ttft_mean_ms / rb.tpot_mean_ms) but deliberately keeps emitting them as ttft_ms / itl_ms "for the collectors" — and end-to-end latency was still absent entirely. So the gate, looking only for the canonical name on the top level, saw an untimed candidate every time. Fixed in two halves:

  • integrate_patch and specialists/rebench now carry end-to-end latency, emitted as e2el_ms to match the siblings beside it so the collector contract is untouched.
  • latency_fields_from_result normalizes what promotion reads: the spellings in flight and the one layer of nesting integrate_patch uses when it returns the measurement under bench_result. The four lift dicts go through it, so current_best carries the latency it was graded on. Before: result.get("e2el_mean_ms")None → refused. After: {'ttft_mean_ms': 40.0, 'e2el_mean_ms': 150.0, 'tpot_mean_ms': 12.0} → passes a 200 ms budget.

On the test: agreed the old ones couldn't have caught this, since every one hardcoded e2el_mean_ms on the lift dict. TestLaneResultShapes now drives the lanes' real payload shapes through _lift_to_current_best, and pins the canonical names against a VariantResult rename so reading a field the object doesn't have can't silently return None again.

2. Report claimed "within budget" unconditionally. Fixed — it compares now. Against a 200 ms budget with 1211 ms kept it reads `1211.0` ms, **over budget by 1011.0 ms**, followed by a line saying no candidate has yet come in under the SLA and that the budget does not refuse the baseline. An untimed current_best says latency not measured rather than claiming compliance.

3. orchestration.md pointed at an invisible field. Added the section rather than dropping the pointer. SharedState.to_latency_budget_summary() renders === Latency budget (constraint) === when a budget is set, and the guidance now names that block:

budget    : 200 ms mean end-to-end, enforced on every KEEP
unmeasured: refused (a constraint that was not measured is not satisfied)
refused   : 2 winner(s) so far
  - cpx-2-streams (explore): 1211 ms
  - qpx-4-streams (framework): not measured
A list that keeps growing means the SLA is the binding limit, not an exhausted search space

4. Resume cleared the env tier before restoring it. Guarded exactly as you suggested. Worth flagging: that fix exposed a latent crash one line further on — with the env no longer popped, an unparseable HYPERLOOM_MAX_LATENCY_MS reached float() and would have ended the resume with a traceback. _restore_latency_budget_from_state now falls through to the archived budget on any unusable env value, matching how resolve_latency_budget_ms treats a zero tier.

5. SKILL.md flag table. Row added, noting it is a constraint that combines with --target-* and that omitting it does not lose a preference but removes the SLA from the search.

What the rebase changed. main retired the post-KEEP confirmation round (_stack_rebench.py deleted) and the framework_agent action (framework_agent.py and _promote_framework_agent deleted). Two pieces of this PR went with them: the StackRebenchResult.e2el_mean_ms plumbing, and the framework lane's lift. Framework candidates now land through integrate_patch, which carries the fix, so the coverage you asked about is intact — but the commit message, CHANGELOG and explore comments claiming the gate grades a rebench's latency were describing code that no longer exists, and are corrected.

One adjacent bug, already dead — no action. While confirming finding 1 I found the same class of bug in the retired lane's result_dir: getattr(r, "result_dir", "") on a VariantResult with no such field, always "", fed straight to parse_eval_results, so framework_agent's accuracy gate could never parse a result and silently stayed None. It was wrong twice over — even populated, it pointed at the benchmark_* workspace, while lm-eval writes to $EVAL_RESULT_DIR one level up under the grid slot. I checked whether it survived into the integrate_patch path: it did not. Every remaining result_dir read in the tree is params.get("result_dir") behind sanitize_result_dir, and integrate_patch grades from override_result_dir or Path(bench["workspace"]).parent. Flagging only in case past FRAMEWORK accuracy verdicts look suspiciously absent.

Verification. test_latency_budget.py 30 → 57 tests, all passing. Full inference_optimizer suite: 11441 passed, 20 pre-existing failures that reproduce identically on pristine 120cb3262 (API-key and cluster-dependent suites). ruff check and ruff format --check clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants